package v2

import (
	"bytes"
	"encoding/xml"
	"errors"
	"github.com/go-emix/utils"
	"math/rand"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

// 统一下单api POST方式
const WxUnifiedOrderApi = "https://api.mch.weixin.qq.com/pay/unifiedorder"

const (
	JSAPITradeType = "JSAPI"
)

const randSource string = "adcdefghjkmnpqrstuvwxyz1234567890ABCDEFGHJKLMNPQRSTUVWXYZ9876543210"

const slen = len(randSource)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// 生成指定位数的随机字符串
func genRandStr(bits int) string {
	builder := strings.Builder{}
	for i := 0; i < bits; i++ {
		intn := rand.Intn(slen)
		builder.WriteByte(randSource[intn])
	}
	return builder.String()
}

type WxUnifiedOrderRequest struct {
	// 小程序ID 必须
	Appid string `xml:"appid"`
	// 商户号 必须
	Mchid string `xml:"mch_id"`
	// 随机字符串 自动生成
	NonceStr string `xml:"nonce_str"`
	// 商品描述 必须
	Body string `xml:"body"`
	// 标价金额 单位分 必须
	TotalFee int `xml:"total_fee"`
	// 商户订单号 必须
	OutTradeNo string `xml:"out_trade_no"`
	// 终端IP 必须
	SpbillCreateIp string `xml:"spbill_create_ip"`
	// 通知地址 必须
	NotifyUrl string `xml:"notify_url"`
	// 交易类型 必须
	TradeType string `xml:"trade_type"`
	// 必须
	Openid string `xml:"openid"`
	// 签名 自动生成
	Sign string `xml:"sign"`
	// 微信支付秘钥 必须
	WxPayKey string `xml:"-"`
	// 附加数据
	Attach string `xml:"attach"`
}

// 微信小程序拉起支付需要的参数
type WxRequestPayment struct {
	// 自己的订单号
	OrderNum  string
	TimeStamp string
	// 对应prepay_id
	PackageStr string `json:"Package"`
	// 随机字符串
	NonceStr string
	// 小程序用的签名
	PaySign string
}

type WxUnifiedOrderResp struct {
	ReturnCode string `xml:"return_code"`
	ReturnMsg  string `xml:"return_msg"`
	ResultCode string `xml:"result_code"`
	ErrCode    string `xml:"err_code"`
	ErrCodeDes string `xml:"err_code_des"`
	PrepayId   string `xml:"prepay_id"`
	NonceStr   string `xml:"nonce_str"`
	Sign       string `xml:"sign"`
}

// 微信统一下单
func WxUnifiedOrder(req WxUnifiedOrderRequest) (*WxRequestPayment, error) {
	req.NonceStr = genRandStr(20)
	if req.TradeType == "" {
		req.TradeType = JSAPITradeType
	}
	sm := make(map[string]string)
	sm["appid"] = req.Appid
	sm["mch_id"] = req.Mchid
	sm["nonce_str"] = req.NonceStr
	sm["body"] = req.Body
	sm["total_fee"] = strconv.Itoa(req.TotalFee)
	sm["out_trade_no"] = req.OutTradeNo
	sm["spbill_create_ip"] = req.SpbillCreateIp
	sm["notify_url"] = req.NotifyUrl
	sm["trade_type"] = req.TradeType
	sm["openid"] = req.Openid
	sm["attach"] = req.Attach
	req.Sign = genSign(sm, req.WxPayKey)
	marshal, err := xml.Marshal(&req)
	if err != nil {
		return nil, err
	}
	all := bytes.ReplaceAll(marshal, []byte("WxUnifiedOrderRequest"), []byte("xml"))
	post, err := http.Post(WxUnifiedOrderApi, "	text/xml", bytes.NewBuffer(all))
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = post.Body.Close()
	}()
	var wur WxUnifiedOrderResp
	err = xml.NewDecoder(post.Body).Decode(&wur)
	if err != nil {
		return nil, err
	}
	if wur.ReturnCode == "FAIL" {
		return nil, errors.New(wur.ReturnMsg)
	} else if wur.ReturnCode == "SUCCESS" {
		resultCode := wur.ResultCode
		if resultCode == "FAIL" {
			return nil, errors.New(wur.ErrCode + "-" +
				wur.ErrCodeDes)
		} else if resultCode == "SUCCESS" {
			pckStr := "prepay_id=" + wur.PrepayId
			nonceStr := wur.NonceStr
			timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
			pm := WxRequestPayment{}
			pm.OrderNum = req.OutTradeNo
			pm.TimeStamp = timeStamp
			pm.PackageStr = pckStr
			pm.NonceStr = nonceStr
			gpm := make(map[string]string)
			gpm["appId"] = req.Appid
			gpm["nonceStr"] = nonceStr
			gpm["package"] = pckStr
			gpm["signType"] = "MD5"
			gpm["timeStamp"] = timeStamp
			pm.PaySign = genSign(gpm, req.WxPayKey)
			return &pm, nil
		}
	}
	return nil, errors.New("未知返回错误")
}

// 排序字段，MD5签名
func genSign(param map[string]string, key string) string {
	pns := make([]string, 0)
	for k := range param {
		pns = append(pns, k)
	}
	sort.Strings(pns)
	bd := strings.Builder{}
	for _, k := range pns {
		v := param[k]
		if v == "" {
			continue
		}
		bd.WriteString(k + "=" + v + "&")
	}
	bd.WriteString("key=" + key)
	return strings.ToUpper(utils.Md5Str(bd.String()))
}
